﻿using System;
using System.Collections.Generic;
using System.Text;
using Rites.GameObjects;
using Rites.Networking;

namespace Rites
{
    class ServerGame
    {
        private ConnectHandler listener;            // This accepts new client connections for the server
        public Dictionary<int, ServerPlayer> _players = new Dictionary<int, ServerPlayer>(); //Indexed master list of players
        public Map localMap { get; set; }

        public void StartNetGame()
        {
            this.listener = new ConnectHandler(Constants.PORT);
            this.listener.userAdded += new UserEventDlg(this.PlayerHasJoined);
            this.listener.Start();

            localMap = new Map();
            localMap.CreateTestMap();
        }

        //New player has connected
        private void PlayerHasJoined(object sender, ServerPlayer joiningPlayer)
        {
            //Register delegates for the player for comms
            joiningPlayer.DataReceived += new DataReceivedDlg(this.DataReceivedFromPlayer);
            joiningPlayer.UserDisconnected += new UserEventDlg(this.PlayerDisconnectedFromServer);

            //Tell the new player what what his global ID is
            //We never use it as an authoritative source of info when it comes from the client
            joiningPlayer.SendMessage(RitesProtocol.PlayerID, joiningPlayer.ID.ToString());

            //Add the new player to the master list of players
            _players.Add(joiningPlayer.ID, joiningPlayer);
        }

        //This sends the message passed in to all connected players
        private void SendToAllPlayers(RitesProtocol messageType, string messageData)
        {
            SendToAllPlayersExcept(messageType, messageData, 99999);
        }

        //This sends the message passed in to all connected players
        private void SendToAllPlayersExcept(RitesProtocol messageType, string messageData, int exceptionID)
        {
            long lastSentMessageID;
            System.IO.MemoryStream memStream = Util.CreateMessage(messageType, messageData, out lastSentMessageID, 0);

            //TODO; Got collection lock error, this needs to be handled differently
            foreach (ServerPlayer plr in _players.Values)
            {
                if (plr.ID != exceptionID)
                {
                    plr.SendMemoryStream(memStream);
                    plr.LastSendMessageID = lastSentMessageID;
                }
            }
        }

        //Player disconnected
        private void PlayerDisconnectedFromServer(object sender, ServerPlayer playerComm)
        {
            //Remove the disconnected player from the list of players and inform others
            _players.Remove(playerComm.ID);
            SendToAllPlayers(RitesProtocol.PlayerDisconnected, playerComm.ID.ToString());
        }

        private void DataReceivedFromPlayer(ServerPlayer sourcePlayer, byte[] data)
        {
            //Convert to string
            string rawBuffer = System.Text.Encoding.ASCII.GetString(data);

            //Add to the relevant player's data buffer
            sourcePlayer.DataBuffer += rawBuffer;

            //Process the buffer, search for messages that have header and footer intact, only process complete messages
            int messageStart = sourcePlayer.DataBuffer.IndexOf(Constants.BOM);
            int messageStop = sourcePlayer.DataBuffer.IndexOf(Constants.EOM);

            while (messageStart >= 0 && messageStop >= 0)
            {
                //Copy complete message from buffer
                string receivedData = sourcePlayer.DataBuffer.Substring(messageStart + Constants.BOM.Length, messageStop - (messageStart + Constants.BOM.Length));

                //Remove message from buffer
                //Note: This will remove requests that are exact duplicates, this might be bad - or good, not sure
                sourcePlayer.DataBuffer = sourcePlayer.DataBuffer.Replace(Constants.BOM + receivedData + Constants.EOM, "");

                //Process this extracted message
                ProcessMessageFromPlayer(sourcePlayer, receivedData);

                //Find next complete message
                messageStart = sourcePlayer.DataBuffer.IndexOf(Constants.BOM);
                messageStop = sourcePlayer.DataBuffer.IndexOf(Constants.EOM);
            }
        }

        private void PopulatePlayer(Player src, Player dest)
        {
            dest.Name = src.Name;
            dest.X = src.X;
            dest.Y = src.Y;
            dest.DestinationX = dest.X;
            dest.DestinationY = dest.Y;
        }

        private void ProcessMessageFromPlayer(ServerPlayer sourcePlayer, string playerMessage)
        {
            //Message Format:
            //<MSG_TYPE> ~ <MSG_ID> ~ <MSG_DATA_PARAMETER#MSG_DATA_PARAMETER#MSG_DATA_PARAMETER...>

            //Extract message parameters
            string[] metaParams = playerMessage.Split('~');
            long messageID = long.Parse(metaParams[1]);
            playerMessage = metaParams[2];
            string[] dataParams = playerMessage.Split('#');

            //Process based on the type of message
            switch ((RitesProtocol)(int.Parse(metaParams[0])))
            {
                case RitesProtocol.PlayerList:
                    foreach (Player plr in _players.Values)
                        sourcePlayer.SendMessage(RitesProtocol.PlayerUpdate, String.Format("{0}#{1}#{2}#{3}", plr.ID, plr.Name, plr.X, plr.Y));

                    break;
                case RitesProtocol.MapData:
                    sourcePlayer.SendMessage(RitesProtocol.MapData, "");
                    break;
                case RitesProtocol.TextCommand:
                    switch (dataParams[0].ToLower())
                    {
                        case "look":
                            sourcePlayer.SendMessage(RitesProtocol.TextCommand, localMap.Locations[sourcePlayer.X, sourcePlayer.Y].Description);
                            break;
                        case "ponder":
                            SendToAllPlayers(RitesProtocol.TextCommand, String.Format("{0} ponders the meaning of it all.", sourcePlayer.Name));
                            break;
                        default:
                            sourcePlayer.SendMessage(RitesProtocol.TextCommand, "Can't do that here.");
                            break;
                    }

                    break;
                case RitesProtocol.PlayerMove:
                    //TODO: Validate move requests
                    sourcePlayer.X = int.Parse(dataParams[0]);
                    sourcePlayer.Y = int.Parse(dataParams[1]);

                    //sourcePlayer.SendMessage(RitesProtocol.PlayerMove, String.Format("{0}#{1}#{2}", dataParams[0], dataParams[1], dataParams[2]));
                    SendToAllPlayers(RitesProtocol.PlayerMove, String.Format("{0}#{1}#{2}", sourcePlayer.ID, sourcePlayer.X, sourcePlayer.Y));
                    break;
                case RitesProtocol.PlayerCredentials:
                    Player plrLoaded = Util.LoadPlayer(dataParams[0]);
                    if (plrLoaded != null)
                    {
                        PopulatePlayer(plrLoaded, sourcePlayer);

                        // Maybe this should be rolled into the PlayerUpdate message. If clients gets PlayerUpdate about previously unknown player, it adds it?
                        SendToAllPlayers(RitesProtocol.PlayerConnected, String.Format("{0}#{1}#{2}#{3}", sourcePlayer.ID, sourcePlayer.Name, sourcePlayer.X, sourcePlayer.Y));

                        //sourcePlayer.SendMessage(RitesProtocol.MapData, localMap.Serialize());
                        sourcePlayer.SendMessage(RitesProtocol.MapData, "");

                        //sourcePlayer.SendMessage(RitesProtocol.TeleportPlayer, String.Format("{0}#{1}", sourcePlayer.X, sourcePlayer.Y));
                        //SendToAllPlayers(RitesProtocol.TeleportPlayer, String.Format("{0}#{1}#{2}", sourcePlayer.ID, sourcePlayer.X, sourcePlayer.Y));
                    }
                    else
                    {
                        _players.Remove(sourcePlayer.ID);
                        sourcePlayer.SendMessage(RitesProtocol.AuthFailed, "");
                        sourcePlayer.Disconnect();
                        sourcePlayer = null;
                    }
                    break;
                case RitesProtocol.Chat: //A chat text from the client
                    //Later on we might validate this so you can't chat to people that are not in the same place as you, but this is global for now
                    SendToAllPlayers(RitesProtocol.Chat, String.Format("{0}#{1}", sourcePlayer.ID, metaParams[2]));
                    break;
                case RitesProtocol.PlayerDisconnected: //Notification from client of a disconnect
                    SendToAllPlayers(RitesProtocol.PlayerDisconnected, sourcePlayer.ID.ToString());
                    sourcePlayer.Disconnect();
                    break;
            }
        }
    }
}
